#region license
/*
 * Copyright (C) 2007 CchenSoft.com
 * 
 * This library is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License 2.1 or later, as
 * published by the Free Software Foundation. See the included License.txt
 * or http://www.gnu.org/copyleft/lesser.html for details.
 * 
 * Author: Billy Zhang
 */
#endregion

using System;
using System.IO;
using System.Collections;
using System.Xml;
using System.Text;
using System.Configuration;

using CchenSoft.Workflow.Util;
using CchenSoft.Common.Utils;

namespace CchenSoft.Workflow.Loader
{
    public class XMLWorkflowFactory : AbstractWorkflowFactory
    {
        //~ Static fields/initializers /////////////////////////////////////////////

        //~ Instance fields ////////////////////////////////////////////////////////

        protected Hashtable workflows;
        protected bool reload;

        //~ Methods ////////////////////////////////////////////////////////////////

        public override void SetLayout(string workflowName, object layout)
        {
        }

        public override object GetLayout(string workflowName)
        {
            return null;
        }

        public override bool IsModifiable(string name)
        {
            return true;
        }

        public override string Name
        {
            get { return ""; }
        }

        public override WorkflowDescriptor GetWorkflow(string name, bool validate)
        {
            WorkflowConfig c = (WorkflowConfig)workflows[name];

            if (c == null)
            {
                throw new FactoryException("Unknown workflow name \"" + name + '\"');
            }

            if (c.descriptor != null)
            {
                if (reload)
                {
                    FileInfo file = new FileInfo(c.filename);

                    if (file.Exists && (file.LastWriteTime > c.lastModified))
                    {
                        c.lastModified = file.LastWriteTime;
                        LoadWorkflow(c, validate);
                    }
                }
            }
            else
            {
                LoadWorkflow(c, validate);
            }

            c.descriptor.Name = name;

            return c.descriptor;
        }

        public override string[] GetWorkflowNames()
        {
            int i = 0;
            string[] res = new string[workflows.Count];
            IEnumerator it = workflows.Keys.GetEnumerator();

            while (it.MoveNext())
            {
                res[i++] = (string)it.Current;
            }

            return res;
        }

        public override void CreateWorkflow(string name)
        {
        }

        public override void InitDone()
        {
            reload = ConvertUtil.GetSafeString(Properties["reload"]).Equals("true");

            string name = ConvertUtil.GetSafeString(Properties["resource"]);
            if (string.IsNullOrEmpty(name))
                name = "workflows.xml";
            Stream stream = GetInputStream(name);

            if (stream == null)
            {
                throw new FactoryException("Unable to find workflows file '" + name + "' in classpath");
            }

            try
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(stream);

                XmlElement root = (XmlElement)doc.DocumentElement; //.SelectNodes("workflows")[0];
                workflows = new Hashtable();

                string basedir = getBaseDir(root);

                XmlNodeList list = root.SelectNodes("workflow");

                for (int i = 0; i < list.Count; i++)
                {
                    XmlElement e = (XmlElement)list[i];
                    WorkflowConfig config = new WorkflowConfig(basedir, XMLUtil.GetAttributeValue(e, "type"), XMLUtil.GetAttributeValue(e, "location"));
                    workflows.Add(XMLUtil.GetAttributeValue(e, "name"), config);
                }
            }
            catch (Exception e)
            {
                throw new InvalidWorkflowDescriptorException("Error in workflow config", e);
            }
            finally
            {
                stream.Close();
            }
        }

        public override bool RemoveWorkflow(string name)
        {
            throw new FactoryException("remove workflow not supported");

            //WorkflowConfig workflow = (WorkflowConfig)workflows.remove(name);
            //if(workflow == null) return false;
            //if(workflow.descriptor != null)
            //{
            //
            //}
            //return true;
        }

        public override void RenameWorkflow(string oldName, string newName)
        {
        }

        public override void Save()
        {
        }

        public override bool SaveWorkflow(string name, WorkflowDescriptor descriptor, bool replace)
        {
            WorkflowConfig c = (WorkflowConfig)workflows[name];

            if ((c != null) && !replace)
            {
                return false;
            }

            if (c == null)
            {
                throw new NotImplementedException("Saving of new workflow is not currently supported");
            }

            StreamWriter writer;

            // [KAP] comment this line to disable all the validation while saving a workflow
            //descriptor.validate();
            try
            {
                writer = new StreamWriter(c.filename + ".new", true, Encoding.UTF8);
            }
            catch (FileNotFoundException ex)
            {
                throw new FactoryException("Could not create new file to save workflow " + c.filename);
            } //catch (UnsupportedEncodingException ex) {
            //  throw new FactoryException("utf-8 encoding not supported, contact your JVM vendor!");
            //}

            WriteXML(descriptor, writer);

            //write it out to a new file, to ensure we don't end up with a messed up file if we're interrupted halfway for some reason
            //now lets rename
            FileInfo original = new FileInfo(c.filename);
            FileInfo backup = new FileInfo(c.filename + ".bak");
            FileInfo updated = new FileInfo(c.filename + ".new");

            try
            {
                original.MoveTo(backup.FullName);
            }
            catch
            {
                throw new FactoryException("Unable to backup original workflow file " + original + " to " + backup + ", aborting save");
            }

            try
            {
                updated.MoveTo(original.FullName);
            }
            catch
            {
                throw new FactoryException("Unable to rename new  workflow file " + updated + " to " + original + ", aborting save");
            }

            backup.Delete();

            return true;
        }

        /**
         * Get where to find workflow XML files.
         * @param root The root element of the XML file.
         * @return The absolute base dir used for finding these files or null.
         */
        protected string getBaseDir(XmlElement root)
        {
            string basedir = XMLUtil.GetAttributeValue(root, "basedir");

            if (basedir.Length == 0)
            {
                // No base dir defined
                return null;
            }

            if (new FileInfo(basedir).Exists)
            {
                // An absolute base dir defined
                return basedir;
            }
            else
            {
                // Append the current working directory to the relative base dir
                return ConfigurationManager.AppSettings["user.dir"];
            }
        }

        /**
         * Load the workflow config file from the current context classloader.
         * The search order is:
         * <li>Specified URL</li>
         * <li>&lt;name&gt;</li>
         * <li>/&lt;name&gt;</li>
         * <li>META-INF/&lt;name&gt;</li>
         * <li>/META-INF/&lt;name&gt;</li>
         */
        protected Stream GetInputStream(string name)
        {
            //ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Stream stream = null;

            if (name != null)
            {
                try
                {
                    stream = new FileInfo(name).OpenRead();
                }
                catch (Exception e)
                {
                }
            }

            if (stream == null)
            {
                try
                {
                    string appPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                    string fullname = Path.Combine(appPath, name);
                    stream = new FileInfo(fullname).OpenRead();
                }
                catch (Exception e)
                {
                }
            }

            //if (stream == null) {
            //    try {
            //        stream = classLoader.getResourceAsStream('/' + name);
            //    } catch (Exception e) {
            //    }
            //}

            //if (stream == null) {
            //    try {
            //        stream = classLoader.getResourceAsStream("META-INF/" + name);
            //    } catch (Exception e) {
            //    }
            //}

            //if (stream == null) {
            //    try {
            //        stream = classLoader.getResourceAsStream("/META-INF/" + name);
            //    } catch (Exception e) {
            //    }
            //}

            return stream;
        }

        protected void WriteXML(WorkflowDescriptor descriptor, StreamWriter writer)
        {
            //PrintWriter writer = new PrintWriter(new BufferedWriter(writer));
            //writer.println(WorkflowDescriptor.XML_HEADER);
            //writer.println(WorkflowDescriptor.DOCTYPE_DECL);
            //descriptor.writeXML(writer, 0);
            //writer.flush();
            //writer.close();
            XmlTextWriter xmlWriter = new XmlTextWriter(writer);
            xmlWriter.WriteStartDocument();
            xmlWriter.WriteDocType(WorkflowDescriptor.DOCTYPE_NAME, WorkflowDescriptor.DOCTYPE_PUB_ID, WorkflowDescriptor.DOCTYPE_SYS_ID, null);
            descriptor.WriteXML(xmlWriter, 0);
            xmlWriter.Flush();
            xmlWriter.Close();
        }

        private void LoadWorkflow(WorkflowConfig c, bool validate)
        {
            try
            {
                using (FileStream stream = new FileInfo(c.filename).OpenRead())
                {
                    c.descriptor = WorkflowLoader.Load(stream, validate);
                }
            }
            catch (Exception e)
            {
                throw new FactoryException("Error in workflow descriptor: " + c.url, e);
            }
        }

        //~ Inner Classes //////////////////////////////////////////////////////////

        internal class WorkflowConfig
        {
            public string location;
            public string type; // file, URL, service
            public Uri url;
            public string filename; // filename
            public WorkflowDescriptor descriptor;
            public DateTime lastModified;

            public WorkflowConfig(string basedir, string type, string location)
            {
                if ("URL".Equals(type))
                {
                    //try {
                    //    url = new URL(location);

                    //    FileInfo file = new FileInfo(url.getFile());

                    //    if (file.Exists) {
                    //        lastModified = file.LastWriteTime;
                    //    }
                    //} catch (Exception ex) {
                    //}
                }
                else if ("file".Equals(type))
                {
                    try
                    {
                        FileInfo file = new FileInfo(basedir + location);
                        //url = file.toURL();
                        if (!file.Exists)
                        {
                            string appPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                            file = new FileInfo(Path.Combine(appPath, location));
                        }

                        lastModified = file.LastWriteTime;
                        filename = file.FullName;
                    }
                    catch (Exception ex)
                    {
                    }
                }
                else
                {
                    //url = Thread.currentThread().getContextClassLoader().getResource(location);
                }

                this.type = type;
                this.location = location;
            }
        }
    }
}
